/*
 * author      : 吴中勤
 * date        : 2018/8/20
 * description : TODO
 */

package com.deepexi.adapter.converter.sap.fund;

import com.deepexi.adapter.common.StringUtil;
import com.deepexi.adapter.common.exception.HttpNon200Exception;
import com.deepexi.adapter.common.sdk.RequestInfo;
import com.deepexi.adapter.config.LoggerManage;
import com.deepexi.adapter.converter.sap.SapConverterContext;
import com.deepexi.adapter.converter.sap.SapResponseHandler;
import com.deepexi.adapter.converter.sap.order.SapAdapterOrder;
import com.deepexi.adapter.converter.sap.order.SapAdapterOrderMapper;
import com.deepexi.adapter.facade.moby.request.MobyEventBody;
import com.deepexi.adapter.facade.moby.request.MobyPayInfo;
import com.deepexi.adapter.facade.moby.request.MobyRefundInfo;
import com.deepexi.adapter.sdk.sap.SapRecords;
import com.deepexi.adapter.sdk.sap.SapSdk;
import com.deepexi.adapter.sdk.sap.dto.fund.SapFinalPaymentRequestBody;
import com.deepexi.adapter.sdk.sap.dto.fund.SapFundRequestBody;
import com.deepexi.adapter.sdk.sap.dto.fund.SapFundResponseBody;
import com.deepexi.adapter.sdk.sap.dto.fund.SapRefundRequestBody;
import com.deepexi.adapter.sdk.sap.exception.SapBizException;
import org.json.JSONException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class FundService {
    private static final Logger logger = LoggerFactory.getLogger(FundService.class);

    /** 一个 payOrderId 最多关联的 款项记录 = 支付/退款 = 2条记录 */
    private static final int MAX_COUNT_OF_PAY_ORDER_NO = 2;

    @Autowired
    private SapAdapterFundMapper fundMapper;

    @Autowired
    private SapAdapterOrderMapper orderMapper;

    @Autowired
    private FundAssembler fundAssembler;

    @Autowired
    private SapSdk sapSdk;

    @Autowired
    private SapConverterContext sapConverterContext;

    @Autowired
    private SapResponseHandler responseHandler;

    @Autowired
    private LockService lockService;


    @LoggerManage(description = "订金支付")
    public List<SapAdapterFund> onDepositPaid(MobyEventBody body)
            throws IOException, JSONException, SapBizException, HttpNon200Exception {
        if (null == body) throw new SapBizException("onDepositPaid, 输入参数为 null");

        return fundBeforeBilling(body);
    }

    @LoggerManage(description = "首付确认")
    public List<SapAdapterFund> onDownPaymentConfirmed(MobyEventBody body)
            throws IOException, JSONException, SapBizException, HttpNon200Exception {
        if (null == body) throw new SapBizException("onDownPaymentConfirmed, 输入参数为 null");
        return fundBeforeBilling(body);
    }

    @LoggerManage(description = "尾款确认")
    public SapAdapterFund onFinalPaymentConfirmed(MobyEventBody body)
            throws IOException, JSONException, SapBizException, HttpNon200Exception {
        if (null == body) throw new SapBizException("onFinalPaymentConfirmed, 输入参数为 null");

        // 过滤出尾款支付信息
        List<MobyPayInfo> toBeSavePayInfo = assureEffectivePayInfo(body);
        if (toBeSavePayInfo.size() == 0) throw new SapBizException("尾款确认, 没有有效的支付记录");
        List<MobyPayInfo> finalPaymentList = toBeSavePayInfo.stream()
                .filter(MobyPayInfo::isFinalPayment)
                .collect(Collectors.toList());
        if (null == finalPaymentList || 0 == finalPaymentList.size()) throw new SapBizException("尾款确认, 没有有效的尾款支付记录");
        if (finalPaymentList.size() > 1) throw new SapBizException("尾款确认, 超过1笔尾款支付记录");

        MobyPayInfo mobyPayInfo = finalPaymentList.get(0);
        SapAdapterOrder oldRecord = orderMapper.findOneByMobyOrder(body.getMobyTradeOrder().getId());
        if (oldRecord == null) throw new SapBizException("尾款确认, 对应的订单找不到");

        SapFinalPaymentRequestBody sapBody = fundAssembler.assembleFinalPayInfo(body, mobyPayInfo, oldRecord.getSapOrderNo());
        sapConverterContext.getLogContext().setPayOrderId(mobyPayInfo.getPayOrderNo());

        // 发送请求并得到结果
        RequestInfo requestInfo = sapSdk.requestInfo4FinalPayment(new SapRecords<>(sapBody));
        SapFundResponseBody responseBody = responseHandler.requestAndThrowIfError(requestInfo);

        // 保存
        SapAdapterFund sapAdapterFund = fundAssembler.fromSapResponse(body, mobyPayInfo, responseBody);
        fundMapper.insert(sapAdapterFund);

        // 返回结果
        return sapAdapterFund;

    }

    /** 订金/定金/首付 都可以走这个逻辑 */
    private List<SapAdapterFund> fundBeforeBilling(MobyEventBody body)
            throws IOException, JSONException, SapBizException, HttpNon200Exception {
        // 过滤有效状态 status = paid
        List<MobyPayInfo> toBeSavePayInfo = assureEffectivePayInfo(body);
        if (toBeSavePayInfo.size() == 0) {
            logger.error("重复提交订金支付: 所有支付信息都已经记录");
            return null;
        }

        if (toBeSavePayInfo.size() > 1)
            logger.warn("同时收到多条资金同步信息");

        Set<String> toSaveSet = new HashSet<>(toBeSavePayInfo.size());
        toBeSavePayInfo.forEach(mobyPayInfo -> {
            toSaveSet.add(mobyPayInfo.getPayOrderNo());
        });

        /* 资金同步, 并发控制: 不允许同时支付 同一订单: 消息同时多次 */
        synchronized (lockService.getLock(body)) {
            List<SapAdapterFund> paidList = fundMapper.listByTradeOrder(body.getMobyTradeOrder().getId());
            Set<String> savedSet = new HashSet<>(paidList.size());
            paidList.forEach(mobyPayInfo -> {
                toSaveSet.add(mobyPayInfo.getPayOrderId());
            });

            // 已经出现了
            if (savedSet.retainAll(toSaveSet)) {
                logger.error("出现同时支付, 忽略");
                lockService.releaseLock(body);
                throw new SapBizException("重复支付, mobyOrderId={}" + body.getMobyTradeOrder().getId());
            }

            List<SapAdapterFund> saveResult = new ArrayList<>(toBeSavePayInfo.size());
            for (int i = 0; i < toBeSavePayInfo.size(); i++) {
                MobyPayInfo mobyPayInfo = toBeSavePayInfo.get(i);

                SapAdapterFund sapAdapterFund = saveOnePaidInfo(body, mobyPayInfo);
                saveResult.add(sapAdapterFund);
            }

            lockService.releaseLock(body);
            return saveResult;
        }
    }


    /** 确保 支付信息列表中, 有 切实有效的支付记录 */
    private List<MobyPayInfo> assureEffectivePayInfo(MobyEventBody body) throws SapBizException {
        List<MobyPayInfo> payInfoList = body.getMobyPayInfoList();
        if (null == payInfoList || 0 == payInfoList.size())
            throw new SapBizException("moby输入的支付信息无效: payInfoList 为null 或 长度=0, 不同步到SAP");

        // 过滤有效状态 status = paid
        List<MobyPayInfo> paidRows = payInfoList
                .stream()
                .filter(MobyPayInfo::isPaid)
                .collect(Collectors.toList());
        if (null == paidRows || 0 == paidRows.size()) {
            throw new SapBizException("moby输入的支付信息无效: 所有的支付记录都不是【paid】状态, 不同步到SAP");
        }

        // 从 paidRows 中删除已经同步过的
        Map<String, SapAdapterFund> savedFund = querySavedFundList(body.getMobyTradeOrder().getId());
        return paidRows.stream()
                .filter(mobyPayInfo ->
                        null == savedFund.get(mobyPayInfo.getPayOrderNo()) // 没有进行过同步
                ).collect(Collectors.toList());
    }

    @LoggerManage(description = "订金退款")
    public void onDepositRefunded(MobyEventBody body)
            throws IOException, JSONException, SapBizException, HttpNon200Exception {

        // 数据有效性校验
        if (null == body)
            throw new SapBizException("订金退款, 输入参数为 null");
        List<MobyRefundInfo> refundList = body.getMobyRefundInfoList();
        if (null == refundList || 0 == refundList.size())
            throw new SapBizException("订金退款, refundList 无效");

        // 过滤已经完成的退款状态

        List<MobyRefundInfo> effectiveList = new ArrayList<>();
        for (MobyRefundInfo info : refundList) {
            if (info.isRefundDone()) {
                effectiveList.add(info);
            } else {
                logger.warn("退款状态未定, 不同步, refundOrderId=" + info.getRefundOrderId());
            }
        }
        if (0 == effectiveList.size())
            throw new SapBizException("onRefundDeposit, 退款记录的状态都不是已退款");

        // 该订单所有的 款项记录
        List<SapAdapterFund> fundListOfTradeOrder = fundMapper.listByTradeOrder(body.getMobyTradeOrder().getId());

        // 对 需要退款的 退款单, 找到与之对应的 付款单
        for (MobyRefundInfo mobyRefundInfo : effectiveList) {
            SapAdapterFund relatedPayinfo = assureOnlyOnePayInfoSaved(mobyRefundInfo, fundListOfTradeOrder);
            if (null != relatedPayinfo) {
                handleRefund(body, relatedPayinfo, mobyRefundInfo);
            }
        }
    }

    /** 支付单条订金/首付 */
    private SapAdapterFund saveOnePaidInfo(MobyEventBody body, MobyPayInfo mobyPayInfo)
            throws IOException, JSONException, SapBizException, HttpNon200Exception {

        SapAdapterOrder oldRecord = orderMapper.findOneByMobyOrder(body.getMobyTradeOrder().getId());
        String sapOrderNo = null == oldRecord ? null : oldRecord.getSapOrderNo();
        SapFundRequestBody sapBody = fundAssembler.assemblePayInfo(body, mobyPayInfo, sapOrderNo);

        sapConverterContext.getLogContext().setPayOrderId(mobyPayInfo.getPayOrderNo());

        // 发送请求并得到结果
        RequestInfo requestInfo = sapSdk.requestInfo4Fund(new SapRecords<>(sapBody));
        SapFundResponseBody responseBody = responseHandler.requestAndThrowIfError(requestInfo);

        // 保存
        SapAdapterFund sapAdapterFund = fundAssembler.fromSapResponse(body, mobyPayInfo, responseBody);
        fundMapper.insert(sapAdapterFund);

        // 返回结果
        return sapAdapterFund;
    }

    /** 已记录的付款信息 */
    private Map<String, SapAdapterFund> querySavedFundList(String tradeOrderId) {
        List<SapAdapterFund> recordList = fundMapper.listByTradeOrder(tradeOrderId);
        Map<String, SapAdapterFund> hadRecord = new HashMap<>();
        recordList.forEach(sapAdapterFund ->
                hadRecord.put(sapAdapterFund.getPayOrderId(), sapAdapterFund)
        );
        return hadRecord;
    }

    /** 从 fundListOfTradeOrder 中找到与 退款相关的记录, 并确认只有一条付款记录 */
    private SapAdapterFund assureOnlyOnePayInfoSaved(MobyRefundInfo mobyRefundInfo, List<SapAdapterFund> fundListOfTradeOrder)
            throws SapBizException {

        String payOrderNo = mobyRefundInfo.getPayOrderNo();
        List<SapAdapterFund> relativeRecordList = fundListOfTradeOrder
                .stream()
                .filter(sapAdapterFund -> sapAdapterFund.getPayOrderId().equals(payOrderNo))
                .collect(Collectors.toList());

        // 对于一个 payOrderId, 最多有2条记录: 一个支付, 一个退款
        int rowCountRelated = relativeRecordList.size();
        if (rowCountRelated > MAX_COUNT_OF_PAY_ORDER_NO) {
            // 数据库异常
            throw new SapBizException("数据库数据不一致, 同样的支付订单有>2条记录, 款项记录 payOrderId=" + payOrderNo);
        }

        if (rowCountRelated == 0) {
            // 需要退款的项目, 找不到任何一条付款记录
            throw new SapBizException("退款时找不到对应的付款记录, payOrderId=" + payOrderNo);
        }

        if (rowCountRelated == 1) {
            SapAdapterFund payRecord = relativeRecordList.get(0);
            if (payRecord.isRefund()) {
                // 数据库有退款记录, 无付款记录, 数据异常
                throw new SapBizException("数据库数据不一致, 有退款记录 无付款记录,payOrderId=" + payOrderNo);
            }
            return payRecord;
        }

        if (rowCountRelated == MAX_COUNT_OF_PAY_ORDER_NO) {
            SapAdapterFund row1 = relativeRecordList.get(0);
            SapAdapterFund row2 = relativeRecordList.get(1);
            if ((row1.isRefund() && !row2.isRefund()) || (!row1.isRefund() && row2.isRefund())) {
                // 一条支付, 一条退款
                logger.error("进行退款,但是已有支付和退款记录, 不进行退款。 payOrderId=" + payOrderNo);
            } else {
                // 数据库异常
                throw new SapBizException("数据库数据不一致, 同样的支付订单有2条记录,不是一付一退, 款项记录 payOrderId=" + payOrderNo);
            }
        }
        return null;
    }

    private void handleRefund(MobyEventBody body, SapAdapterFund payRecord, MobyRefundInfo refundInfo)
            throws IOException, JSONException, SapBizException, HttpNon200Exception {
        SapRefundRequestBody sapBody = fundAssembler.assembleRefund(body, payRecord, refundInfo);
        sapConverterContext.getLogContext().setPayOrderId(refundInfo.getPayOrderNo());

        // 发送请求并得到结果
        RequestInfo requestInfo = sapSdk.requestInfo4Refund(new SapRecords<>(sapBody));
        SapFundResponseBody responseBody = responseHandler.requestAndThrowIfError(requestInfo);

        // 保存
        SapAdapterFund toSave = new SapAdapterFund();
        BeanUtils.copyProperties(payRecord, toSave);
        toSave.refundYes();
        toSave.setRefundAt(refundInfo.getUpdatedAt());
        if (StringUtil.isNullorEmpty(refundInfo.getRefundMoney())) {
            toSave.setRefundMoney(payRecord.getPayMoney());
        }
        fundMapper.insert(toSave);
    }


}
